![]() |
JSTL 1.0: What JSP Applications Need, Part 3by Hans Bergsten, author of JavaServer Pages, 2nd Edition10/30/2002 |
Previous installments in this series gave you an overview of JSTL--the new specification of commonly needed JSP tag libraries--and showed you how to use the core, internationalization, and database JSTL actions, as well as how to use JSTL effectively in an MVC application. In this final installment, we'll look at how you can leverage the JSTL classes when you develop your own custom actions. To understand what I describe here, you need to be a Java programmer and also know a thing or two about how to develop JSP custom actions in general.
The JSTL specification introduces an Expression Language (EL) that can be used to set JSTL action attributes to values computed at runtime, as you have seen in the previous parts of this series. A common question is "Can I also use the EL for setting attribute values in my custom actions?" The answer is: yes and no.
A JSP 1.2 container doesn't know anything about EL expressions, so they are
evaluated by code in the JSTL tag handlers. JSTL 1.0 doesn't define a public API
for this evaluation code, so there's no way to let custom tag handlers do the
same in a way that works with all JSTL implementations. You can, however, pick
one JSTL implementation and code to its API. For instance, if you're willing to
be dependent on the JSTL Reference Implementation (RI) developed in the Apache
Taglibs project (see the Resource section), you can use this
org.apache.taglibs.standard.lang.support.ExpressionEvaluatorManager
method in your tag handler:
public static Object evaluate(String attributeName,
String expression,
Class expectedType,
Tag tag,
PageContext pageContext);
This method takes an EL expression and evaluates it in the specified
PageContext
and converts (coerces) the result to the specified
expected type, according to the rules defined by the JSTL 1.0 specification. The
other parameters are used to include details in a possible error message, such
as the name of the custom action and the attribute where the invalid expression
is used.
In This Series JSTL 1.0: What JSP Applications Need, Part 2 -- Part 2 of our JSTL series focuses on internationization, localization, and database access. JSTL 1.0: Standardizing JSP, Part 1 -- JSTL offers a set of standardized JSP custom actions to handle common tasks. This article, the first in a series, provides an overview and shows how to use the most common tags. |
You must call this method in one of the main methods in the tag handler (e.g.
doEndTag()
), never in the attribute setter
method. The
reason for this is that the setter
method may not be called every
time the tag handler is used, as I describe in my article, "JSP 1.2:
Great News for the JSP Community, Part 2" (in the Tag handler life
cycle and instance reuse section).
Here's a tag handler that accepts an EL expression as the value of its
name
attribute:
package com.ora.jstl;
import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.TagSupport;
import org.apache.taglibs.standard.lang.support.ExpressionEvaluatorManager;
public class HelloTag extends TagSupport {
private String nameEL;
public void setName(String name) {
nameEL = name;
}
public int doEndTag() throws JspException {
String name = (String)
ExpressionEvaluatorManager.evaluate("name", nameEL,
String.class, this, pageContext);
if (name == null || name.length() == 0) {
name = "World";
}
try {
pageContext.getOut().write("Hello " + name + "!");
}
catch (IOException e) {}
return EVAL_PAGE;
}
}
Note that to compile and use this tag handler, you must have the JSTL RI
ExpressionEvaluatorManager
in the classpath; it's available in the
standard.jar file that's part of the RI download.
Related Reading ![]() |
If you can hold your horses a bit and wait for JSP 2.0, you don't have to do anything in your tag handlers to accept EL expression attribute values. JSP 2.0 (currently at the Proposed Final Draft stage, expected to be released Q1 2003) will include a somewhat extended version of the EL and will evaluate EL expressions before calling tag handler attribute setter methods. Hence, EL expressions can be used with any tag handler that is declared in the TLD to accept a runtime value. JSP 2.0 will also accept EL expressions anywhere in the template text.
The JSTL specification group realized that no matter how many custom actions JSTL defines, there will always be a need for application-dependent custom actions. We tried to make it as easy as possible to develop custom actions that integrate nicely with the actions defined by the JSTL specification by including a number of public interfaces and base classes in the specification. The following sections show you some examples of how to use these classes and interfaces, starting with custom conditional actions.
The generic <c:if>
and <c:when>
actions, using the Boolean value of an EL expression as the condition, work
great in many scenarios, but not in all. For instance, say you want to
conditionally add some content depending on the time of day. You could create
your own bean with Boolean properties suitable for use in an EL expression, but
a custom action like this may be more convenient to use:
<xmp:ifAfternoon>
Sorry, we only accept delivery requests before noon.
</xmp:ifAfternoon>
It's very easy to implement such a custom action, thanks to the extendable
JSTL classes. Here's the complete tag handler code for the
<xmp:ifAfternoon>
action:
package com.ora.jstl;
import java.util.Calendar;
import java.util.GregorianCalendar;
import javax.servlet.jsp.jstl.core.ConditionalTagSupport;
public class IfAfternoonTag extends ConditionalTagSupport {
public boolean condition() {
Calendar now = new GregorianCalendar();
return now.get(Calendar.AM_PM) == Calendar.PM;
}
}
The tag handler class extends the JSTL
javax.servlet.jsp.jstl.core.ConditionalTagSupport
class, which
provides implementations for all standard JSP tag handler methods and calls the
condition()
method implemented by the subclass to decide what to
do. It also includes setter methods for var
and scope
attributes, so a subclass like the one shown here behaves just like the
<c:if>
action: if the condition()
method returns
true
, the custom action's body is processed; if it doesn't have a
body, the value can be saved as a Boolean
in variable and scope
specified by the var
and scope
attributes.
You may expect there to be a similar base class for developing custom actions
to be used, the same as a <c:when>
action within a
<c:choose>
block, but there isn't. The reason is this: to
ensure that custom actions cannot interfere with the somewhat complex
interaction between the <c:choose>
action and its nested
actions, only <c:when>
and <c:otherwise>
actions are allowed as direct children of the <c:choose>
action. But you can combine your own conditional actions with a
<c:choose>
like this to get the same multiple-choice
effect:
<xmp:ifAfternoon var="isAfternoon" />
<c:choose>
<c:when test="${isAfternoon}">
Good day!
</c:when>
<c:otherwise>
Good morning!
</c:otherwise>
</c:choose>
Simply save the custom conditional result, using the var
attribute, and then use this result in the EL expression for a
<c:when>
action in the block.
Developing a custom iteration action can also be simplified by extending a
JSTL base class, and custom actions nested within a JSTL
<c:forEach>
action body have easy access to iteration status
information through a JSTL interface.
Let's look at a custom iteration action first. The JSTL base class you can
extend is javax.servlet.jsp.jstl.core.LoopTagSupport
. All you
really need to implement in the subclass are three methods:
prepare()
, hasNext()
, and next()
. This
gives you iteration plus support for the same var
and
varStatus
attributes as the JSTL <c:forEach>
action. If you want to support the begin
, end
, and
step
attributes, the base class provides protected fields and
validation methods, but you have to implement the setter methods yourself (since
not all subclasses need them, and the details differ, depending on if EL
expressions are allowed or not).
To see how you can extend the JSTL base class for your own iteration action, let's develop a custom action that iterates through all days in the current month. The tag handler looks like this:
package com.ora.jstl;
import java.util.Calendar;
import java.util.GregorianCalendar;
import javax.servlet.jsp.jstl.core.LoopTagSupport;
public class ForEachDayTag extends LoopTagSupport {
private Calendar calendar;
private int previousMonth;
public void prepare() {
calendar = new GregorianCalendar();
calendar.set(Calendar.DAY_OF_MONTH, 1);
// Set to last day in previous month, since next() increments it
calendar.add(Calendar.DAY_OF_MONTH, -1);
previousMonth = calendar.get(Calendar.MONTH);
}
public boolean hasNext() {
int currentMonth = calendar.get(Calendar.MONTH);
int currentDay = calendar.get(Calendar.DAY_OF_MONTH);
int lastDay = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
return currentMonth == previousMonth || currentDay < lastDay;
}
public Object next() {
calendar.set(Calendar.DAY_OF_MONTH,
calendar.get(Calendar.DAY_OF_MONTH) + 1);
return calendar;
}
}
The base class calls prepare()
once, followed by a sequence of
calls to hasNext()
and next()
until
hasNext()
returns false
. The subclass code is pretty
straight forward. The prepare()
method creates a
GregorianCalendar
instance and sets it to the last day of the
previous month. The hasNext()
method returns true
if
the day currently represented by the calendar is either a day in the previous
month (i.e., before the first iteration) or a day other than the last day of the
current month. The next()
method, finally, moves the calendar to
the next day and returns the adjusted calendar.
Here's an example of how you can use this custom iterator to generate an HTML table with a cell for each day in the current month:
<table>
<xmp:forEachDay var="curr">
<tr>
<td>
<fmt:formatDate value="${curr.time}"
pattern="EE dd, MMM yyyy" />
</td>
</tr>
</xmp:forEachDay>
</table>
It would be fairly easy to extend this custom action to support the
begin
, end
, and step
attributes, and
maybe an attribute for setting the month to iterate over. I leave that as an
exercise for you to try out on your own.
What if you want to do things only for certain items in the body of an
iteration action? The JSTL <c:forEach>
action and custom
actions extending the LoopTagSupport
base class expose information
about the current item through a variable named by the varStatus
attribute. This variable is an instance of a bean with properties like
first
, last
, index
, and more (see the
JSTL specification for details). For instance, you can use it like this to get
alternating colors for the rows in a table:
<table>
<xmp:forEachDay var="curr" varStatus="stat">
<c:set var="bg" value="white" />
<c:if test="${stat.index % 2 == 0}">
<c:set var="bg" value="blue" />
</c:if>
<tr bgcolor="<c:out value="${bg}" />">
<td>
<fmt:formatDate value="${curr.time}"
pattern="EE dd, MMM yyyy" />
</td>
</tr>
</xmp:forEachDay>
</table>
Sometimes it's impossible to use an EL expression testing the status bean properties (or the current item itself) to figure out if special processing is needed or not. With the calendar iterator, for instance, you can't use an EL expression to find out what day in the week the current item represents. This is where a custom action specifically intended for use within an iterator action body can come in handy.
A custom action can use the knowledge that a JSTL iterator action implements
the javax.servlet.jsp.jstl.core.LoopTag
interface to get access to
the current item and the iteration status iformation. Here's the tag handler
code for a custom action that processes its body only if the current item
represents a Sunday:
package com.ora.jstl;
import java.util.Calendar;
import java.util.GregorianCalendar;
import javax.servlet.jsp.JspTagException;
import javax.servlet.jsp.jstl.core.ConditionalTagSupport;
import javax.servlet.jsp.jstl.core.LoopTag;
public class IfSundayTag extends ConditionalTagSupport {
public boolean condition() throws JspTagException {
LoopTag parent =
(LoopTag) findAncestorWithClass(this, LoopTag.class);
if (parent == null) {
throw new JspTagException("ifSunday must be used in loop");
}
Calendar current = (Calendar) parent.getCurrent();
return current.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY;
}
}
The LoopTag
interface declares two methods:
getCurrent()
returns the current iteration item as an
Object
and getLoopStatus()
returns an instance of
LoopStatus
(the same type as for the object exposed as the
varStatus
variable). The interface is implemented by the
LoopTagSupport
base class, so all tag handlers that extend this
class get the correct behavior for free.
In this example tag handler, the parent that implements the
LoopTag
interface (our ForEachDayTag
tag handler) is
located using the findAncestorWithClass()
method and the current
item is retrieved by calling the parent's getCurrent()
method. If
the current item represents a Sunday, the condition()
method
returns true
. With this custom action, it's easy to do whatever you
want with Sundays:
<table>
<xmp:forEachDay var="curr">
<c:set var="bg" value="white" />
<xmp:ifSunday>
<c:set var="bg" value="red" />
</xmp:ifSunday>
<tr bgcolor="<c:out value="${bg}" />">
<td>
<fmt:formatDate value="${curr.time}"
pattern="EE dd, MMM yyyy" />
</td>
</tr>
</xmp:forEachDay>
</table>
A custom action that needs to do something only for the first or last
iteration, or perhaps only for every second or third iteration, can use the
getLoopStatus()
method to get the information it needs.
There's one more JSTL class that you may find useful when you develop custom
actions: the javax.servlet.jsp.jstl.fmt.LocaleSupport
class. This
class provides methods for getting localized messages from a
ResourceBundle
, using the same algorithms as the JSTL i18n actions
for determining the appropriate locale (as I described in part 2 of
this article series).
The class provides the following methods:
public static String getLocalizedMessage(PageContext pc,
String key);
public static String getLocalizedMessage(PageContext pc, String key,
String basename);
public static String getLocalizedMessage(PageContext pc, String key,
Object[] args);
public static String getLocalizedMessage(PageContext pc, String key,
Object[] args, String basename);
The first two methods get a simple localized message for the specified key.
The second method uses the specified basename to locate the correct
ResourceBundle
, while the first one uses the bundle selected for
the current localization context. The second pair of methods are for
parameterized messages, using the args
parameter to set the message
parameters.
If you've read all parts of this article series, you have a glimpse of what JSTL 1.0 has to offer, whether you're a page author or a programmer. I've covered all features except the JSTL XML processing tag library; it works pretty much the same as the other libraries and if you know XML and XPath, I'm sure you can figure out how to use it on your own. If you don't know XML and XPath, that's where you need to start, and I'm afraid that's out of scope for this article.
While you can get an idea about the possibilities from reading an article, the only way to really learn how to use a technology is to do just that: use it! The Resouce section gives you some pointers to where you can find out more about JSTL and where to ask questions. I hope you'll find JSTL both fun and useful.
Hans Bergsten is the founder of Gefion Software and author of O'Reilly's JavaServer Pages, 3rd Edition.
Return to ONJava.com.
Copyright © 2004 O'Reilly Media, Inc.